home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1993 July / InfoMagic USENET CD-ROM July 1993.ISO / sources / unix / volume25 / npasswd / part02 < prev    next >
Encoding:
Text File  |  1991-12-19  |  50.6 KB  |  1,760 lines

  1. Newsgroups: comp.sources.unix
  2. From: clyde@emx.utexas.edu (Clyde Hoover)
  3. Subject: v25i075: npasswd - replacement for passwd(1), Part02/03
  4. Sender: sources-moderator@pa.dec.com
  5. Approved: vixie@pa.dec.com
  6.  
  7. Submitted-By: clyde@emx.utexas.edu (Clyde Hoover)
  8. Posting-Number: Volume 25, Issue 75
  9. Archive-Name: npasswd/part02
  10.  
  11. From clyde@emx.utexas.edu Fri Jan 25 12:45:25 1991
  12. Received: from BBN.COM by pineapple.bbn.com id <AA22522@pineapple.bbn.com>; Fri, 25 Jan 91 12:45:04 -0500
  13. Received: from uunet.UU.NET by BBN.COM id aa03147; 25 Jan 91 12:38 EST
  14. Received: from cs.utexas.edu by uunet.uu.net (5.61/1.14) with SMTP 
  15.     id AA17158; Fri, 25 Jan 91 12:38:31 -0500
  16. Received: from emx.utexas.edu by cs.utexas.edu (5.64/1.93) via SMTP
  17.     id AA17940; Fri, 25 Jan 91 11:37:52 -0600
  18. Posted-Date:  25 Jan 91 17:02:50 GMT
  19. Received: by emx.utexas.edu (5.61/1.8)
  20.     id AA04098; Fri, 25 Jan 91 11:02:54 -0600
  21. To: comp-sources-unix@emx.utexas.edu
  22. Path: ut-emx!clyde
  23. From: Head UNIX Hacquer <ut-emx!clyde@emx.utexas.edu>
  24. Newsgroups: comp.sources.unix
  25. Subject: npasswd, a replacement for passwd(1) (part 2 of 3)
  26. Keywords: Password changing program
  27. Message-Id: <43165@ut-emx.uucp>
  28. Date: 25 Jan 91 17:02:50 GMT
  29. Organization: Moose & Squirrel Software
  30. Lines: 1725
  31. Status: R
  32.  
  33.  
  34. Npasswd is a pretty-much-plug-compatable replacement for passwd(1).
  35. This version incorporates a password checking system
  36. that disallows simple-minded passwords.
  37.  
  38. It does exactly ONE thing - change login passwords, though it would
  39. not be too difficult to make it do shells and GECOS stuff also.
  40.  
  41. I have modeled npasswd after passwd(1) from 4.3BSD and SunOS 4.0, but
  42. it does not impliment the options those versions have.
  43. I have also included support for Sys VR3 password aging.
  44.  
  45. This version runs at our site under SunOS 4.X, Ultrix 4.0, UMAX 4.3 and
  46. MORE/BSD.
  47.  
  48. It is also available via anonymous FTP from emx.utexas.edu in the directory
  49. pub/npasswd.
  50.  
  51. ----------- cut here -----------
  52. #! /bin/sh
  53. # This is a shell archive.  Remove anything before this line, then unpack
  54. # it by saving it into a file and typing "sh file".  To overwrite existing
  55. # files, type "sh file -c".  You can also feed this as standard input via
  56. # unshar, or by typing "sh <file", e.g..  If this archive is complete, you
  57. # will see the following message at the end:
  58. #        "End of archive 2 (of 3)."
  59. # Contents:  Makefile.dist checkpasswd/README checkpasswd/checkpasswd.8
  60. #   checkpasswd/pwck_dict.c checkpasswd/pwck_lexical.c pw_passwd.c
  61. #   pw_userinfo.c
  62. # Wrapped by clyde@tigger.cc.utexas.edu on Fri Jan 25 10:35:07 1991
  63. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  64. if test -f 'Makefile.dist' -a "${1}" != "-c" ; then 
  65.   echo shar: Will not clobber existing file \"'Makefile.dist'\"
  66. else
  67. echo shar: Extracting \"'Makefile.dist'\" \(7149 characters\)
  68. sed "s/^X//" >'Makefile.dist' <<'END_OF_FILE'
  69. X
  70. X# --------------------------------------------------------------------  #
  71. X#                                                                       #
  72. X#                         Author: Clyde Hoover                          #
  73. X#                          Computation Center                           #
  74. X#                   The University of Texas at Austin                   #
  75. X#                          Austin, Texas 78712                          #
  76. X#                         clyde@emx.utexas.edu                          #
  77. X#                   uunet!cs.utexas.edu!ut-emx!clyde                    #
  78. X#                                                                       #
  79. X#This code may be distributed freely, provided this notice is retained. #
  80. X#                                                                       #
  81. X# --------------------------------------------------------------------  #
  82. X#
  83. X#    Makefile for npasswd
  84. X
  85. X#    @(#)Makefile.dist    1.11 11/26/90 (cc.utexas.edu)
  86. X
  87. X#    Top of install tree
  88. XDEST    =
  89. X
  90. X#    Where the binary lives
  91. XBINDIR = /bin
  92. X
  93. X#    Where condfiguration files live
  94. XADMDIR    = /usr/adm
  95. X
  96. X#    Where manual pages live
  97. XMANDIR    = /usr/man/man1
  98. X
  99. X#    Mode for binary
  100. XIMODE    = 4511
  101. X
  102. X#    Name of binary
  103. XPASSWD    = npasswd
  104. X
  105. X#    Things to build in subdirectories
  106. XSUBDIRS = checkpasswd 
  107. X
  108. X#    Name of password check library
  109. XCHKLIB = checkpasswd/checkpasswd.a
  110. X
  111. X#    Name of distribution files
  112. XDISTNAME = npasswd
  113. X
  114. X#    Name of library to get Sun RPC client routines from
  115. XLIB_RPCSVC    = -lrpcsvc 
  116. X
  117. X#    Flags to pass down to password checker
  118. XCHECKPW_FLAGS    =
  119. X
  120. X# ---------------------------------------------------------------
  121. X#    Npasswd configuration
  122. X# ---------------------------------------------------------------
  123. X
  124. X#    The password file location
  125. X# PWF = /etc/passwd
  126. X
  127. X#    The temp and lock file for passwd file changes
  128. X# PWT = /etc/ptmp
  129. X
  130. X#    The saved password file
  131. X# PWS = /etc/opasswd
  132. X
  133. X# PWFD = -DPASSWD_FILE=\"$(PWF)\"
  134. X# PWTD = -DPASSWD_TEMP=\"$(PWT)\"
  135. X# PWSD = -DPASSWD_SAVE=\"$(PWS)\"
  136. X
  137. X#    The configuration file
  138. XCF = $(DEST)$(ADMDIR)/$(PASSWD).conf
  139. XCFD = -DCONFIG_FILE=\"$(CF)\"
  140. X
  141. X#    The help file
  142. XHF = $(DEST)$(ADMDIR)/$(PASSWD).help
  143. XHFD = -DHELP_FILE=\"$(HF)\"
  144. X
  145. X#    The message file
  146. XMF = $(DEST)$(ADMDIR)/$(PASSWD).motd
  147. XMFD = -DMOTD_FILE=\"$(MF)\"
  148. X
  149. X# ---------------------------------------------------------------
  150. X#    Enable only ONE of the following options
  151. X# ---------------------------------------------------------------
  152. X
  153. X#
  154. X#    Ultrix 4.0 (highly mutated 4.2)
  155. X# ULTRIX    = -DBSD4_3 -DNDBM -DNO_CLNT_SPERRNO -DXFGETPWENT -DXPUTPWENT
  156. X# LIB_RPCSVC    =
  157. X
  158. X#    Using 4.3BSD hashed password file
  159. X# BSD = -DBSD4_3 -DNDBM
  160. X
  161. X#    Running under System V
  162. X# SYS5 = -DSYSV
  163. X
  164. X#    If running under SunOS 4.X (funky tty ioctls)
  165. X# SUNOS = -DSUNOS4
  166. X
  167. X
  168. X# ---------------------------------------------------------------
  169. X#    Any number of the following options can be enabled
  170. X# ---------------------------------------------------------------
  171. X
  172. X#    Use syslog(3) for recording password changes and errors
  173. XSYSLOG = -DSYSLOG 
  174. X
  175. X#    Use private version of getpass(3) which is better behaved than
  176. X#    the version in libc (at least the 4.3BSD version).
  177. XGETPASS = -DXGETPASS
  178. X
  179. X
  180. X#    -DXPUTPWENT provides putpwent() if not in libc.
  181. X# PUTPWENT = -DXPUTPWENT
  182. X
  183. X#    -DXFGETPWENT provides fgetpwent() if not in libc.
  184. X# FGETPWENT = -DXFGETPWENT
  185. X
  186. X# ---------------------------------------------------------------
  187. X#    Program building
  188. X# ---------------------------------------------------------------
  189. X
  190. X#    Debugging switches
  191. XDEBUG    = -g -DDEBUG
  192. X
  193. X#    'XFLAGS' are the configuration flags exported to sub-makes.
  194. X#    Change '-O' in XFLAGS to $(DEBUG) for development work,
  195. X#    change to '-g' to use source debugger.
  196. XXFLAGS    = -O $(BSD) $(SYS5) $(SUNOS) $(ULTRIX)
  197. X
  198. X#    'CFLAGS' are the flags for npasswd only
  199. XCFLAGS    = $(XFLAGS) $(SYSLOG) $(GETPASS) $(PUTPWENT) $(FGETPWENT) \
  200. X    $(HFD) $(CFD) $(MFD) $(PWFD) $(PWTD) $(PWSD)
  201. X
  202. X#    Change the following line to $(DEBUG) for debugging
  203. XLDFLAGS    = 
  204. X
  205. X# ---------------------------------------------------------------
  206. X#    Start of make rules
  207. X# ---------------------------------------------------------------
  208. X#
  209. X#    Remove the leading comment of ONE of the following entries
  210. X#    for 'all'
  211. X#
  212. X# all:    yp_passwd        # Build YP version
  213. X# all:    pw_passwd        # Build standard version
  214. X# all:    ui_passwd        # Build UT CC userinfo version
  215. X
  216. Xfirst::
  217. X    -@echo Do \"make yp_passwd\" to build YP/NIS version.
  218. X    -@echo Do \"make pw_passwd\" to build standard version.
  219. X    -@echo
  220. X    -@echo You should also edit this Makefile to pick the
  221. X    -@echo target for 'all' and configure npasswd for your system.
  222. X
  223. X# ---------------------------------------------------------------
  224. X#    Standard password file version
  225. X# ---------------------------------------------------------------
  226. XOBJ_PW    = npasswd.o pw_passwd.o $(CHKLIB)
  227. X
  228. Xpw_passwd:    $(OBJ_PW)
  229. X    $(CC) -o $(PASSWD) $(LDFLAGS) $(OBJ_PW) 
  230. X
  231. X# ---------------------------------------------------------------
  232. X#    Yellow Pages version
  233. X# ---------------------------------------------------------------
  234. XOBJ_YP    = npasswd.o pw_yp.o $(CHKLIB)
  235. X
  236. Xyp_passwd:    $(OBJ_YP)
  237. X    $(CC) -o $(PASSWD) $(LDFLAGS) $(OBJ_YP) $(LIB_RPCSVC)
  238. X
  239. X# ---------------------------------------------------------------
  240. X#    UTEXAS CC database version
  241. X# ---------------------------------------------------------------
  242. XOBJ_UI    = npasswd.o pw_userinfo.o $(CHKLIB)
  243. X
  244. Xui_passwd:    $(OBJ_UI)
  245. X    $(CC) -o $(PASSWD) $(LDFLAGS) $(OBJ_UI) -luserinfo 
  246. X
  247. X# ---------------------------------------------------------------
  248. X#    Make password checker library
  249. X# ---------------------------------------------------------------
  250. X$(CHKLIB):
  251. X    cd checkpasswd; \
  252. X    make $(MFLAGS) "CFLAGS=$(XFLAGS)" $(CHECKPW_FLAGS) checkpasswd.a
  253. X
  254. X
  255. X# ---------------------------------------------------------------
  256. X#    Misc stuff
  257. X# ---------------------------------------------------------------
  258. Xclean::
  259. X    -rm -f *.o a.out n*passwd core
  260. X    -rm -f passwd passwd.dir passwd.pag opasswd
  261. X    -for f in $(SUBDIRS); do \
  262. X        (cd $$f; make clean); done
  263. X
  264. X# ---------------------------------------------------------------
  265. Xinstall:    $(PASSWD)
  266. X    @if [ `whoami` != root ]; then\
  267. X        echo Must be super-user to install; \
  268. X        exit 1; \
  269. X    else \
  270. X        exit 0; \
  271. X    fi
  272. X    install -s -m $(IMODE) $(PASSWD) $(DEST)$(BINDIR)/$(PASSWD)
  273. X    @if [ ! -r $(CF) ]; then\
  274. X        echo install -c -m 0644 npasswd.conf $(CF);\
  275. X        install -c -m 0644 npasswd.conf $(CF);\
  276. X    else \
  277. X        echo $(CF) already exists.. edit to change; fi
  278. X    @if [ ! -r $(HF) ]; then\
  279. X        echo install -c -m 0644 npasswd.help $(HF);\
  280. X        install -c -m 0644 npasswd.help $(HF);\
  281. X    else \
  282. X        echo $(HF) already exists.. edit to change; fi
  283. X    @echo Put site-specific information in $(MF)
  284. X
  285. Xinstall.man::
  286. X    @echo Customize a manual page
  287. X#    install -c -m 0444 npasswd.1 $(DEST)$(MANDIR)
  288. X
  289. X# ---------------------------------------------------------------
  290. X#    Make copy of password file for testing
  291. Xsetup::
  292. X    -rm -f passwd passwd.dir passwd.pag passwd.old
  293. X    cp /etc/passwd passwd
  294. X    -if [ -f /etc/mkpasswd ]; then \
  295. X        /etc/mkpasswd passwd; fi
  296. X
  297. X# ---------------------------------------------------------------
  298. Xdist::
  299. X    @stuff/makedist $(DISTNAME)
  300. X# ---------------------------------------------------------------
  301. X#
  302. X# Source dependancies
  303. X#
  304. Xnpasswd.o:    npasswd.c version.h
  305. Xpw_passwd.o:    pw_passwd.c
  306. Xpw_yp.o:    pw_yp.c
  307. Xpw_userinfo.o:    pw_userinfo.c
  308. X
  309. X#    End Makefile.dist
  310. END_OF_FILE
  311. if test 7149 -ne `wc -c <'Makefile.dist'`; then
  312.     echo shar: \"'Makefile.dist'\" unpacked with wrong size!
  313. fi
  314. # end of 'Makefile.dist'
  315. fi
  316. if test -f 'checkpasswd/README' -a "${1}" != "-c" ; then 
  317.   echo shar: Will not clobber existing file \"'checkpasswd/README'\"
  318. else
  319. echo shar: Extracting \"'checkpasswd/README'\" \(6143 characters\)
  320. sed "s/^X//" >'checkpasswd/README' <<'END_OF_FILE'
  321. XREADME for checkpasswd
  322. X
  323. X    @(#)README    1.1 5/18/89
  324. X
  325. X** NOTE **
  326. XOriginally the 'mdbm' library was to be distributed along with this program,
  327. Xbut license problems have led to its removal.  There are hooks in some of the
  328. Xcode for "MDBM".  Should I locate with a public domain DBM workalike, 
  329. Xthose hooks will be reworked to use it and it will be distributed.
  330. X
  331. X* Why this code...
  332. X
  333. XThe infamous Internet worm of November 1988 used among its attacks
  334. Xa password guesser.  It is frightening how many passwords on how many UNIX
  335. Xsystems were guessed by this algorithim.  With the publication of various
  336. Xpapers about the internals of the worm, the password cracking algorithims
  337. Xcame to light.
  338. X
  339. XI replicated the worm password cracker from those papers, ran it
  340. Xagainst my password file and got too many hits to be comfortable.
  341. XI decided that some more rigourous validity checking was needed for passwords.
  342. X
  343. XSo I took the worm algorithim and rewrote it into a password checker.
  344. X
  345. XA previous version of this code has appeared on comp.sources.misc.
  346. X
  347. X* What this code does...
  348. X
  349. XThe sequence of checks done is:
  350. X
  351. X    1. Enforcement of a minimal length.
  352. X
  353. X    2. Simple 'lexical' checks to catch some dumb passwords, such as
  354. X       repeats of the same letter (e.g. 'aaa').  Strange characters
  355. X       are also checked for.
  356. X
  357. X    3. Password is compared against some host-specific information, such as
  358. X       the hostname.
  359. X
  360. X    4. Password is checked against a number of permutations on the users'
  361. X       passwd information (login name reversed, login name duplicated).
  362. X
  363. X    5. Password is checked against the user's FINGER information.
  364. X
  365. X    6. A list of dictionaries are scanned for the password.
  366. X
  367. XAll string comparsions are case-independant.
  368. X
  369. XWhat kinds of passwords will be accepted:
  370. X    Passwords which use mixed upper and lower case.
  371. X    Passwords which use punctuation characters.
  372. X    Passwords which use numbers.
  373. X    Passwords which use allowed control characters.
  374. X
  375. XBy default, the following control characters are NOT allowed in passwords:
  376. X    control-c control-d control-h control-j control-m
  377. X    control-o control-r control-s control-q control-y
  378. X    control-z control-\ control-[ control-] DELETE
  379. X
  380. XThese are typical tty special characters on UCB-derived UNIX systems.
  381. XA user could put these characters in their password by quoting them
  382. Xwith control-v, but there is no guarantee that the tty modes
  383. Xset by login(1) would allow them to be properly entered, so they
  384. Xare best avoided.
  385. X
  386. XThis table may be replaced or supplimented via the configuration file.
  387. X
  388. X* About checkpasswd....
  389. X
  390. XThe checking routine is table driven (in checkpasswd.c) so that new routines
  391. Xcan be added easily.
  392. X
  393. XI did not use some other password check routines that have been made available
  394. Xbecause they rejected many passwords which I thought was reasonable.  These
  395. Xchecks are concerned with filtering out words that might be easily guessed.
  396. XI believe that my tests, especially the dictionary lookup, perform this
  397. Xfunction.  Checks can be inserted or deleted as desired.
  398. X
  399. XThis version is much more functional and configurable that the original
  400. Xsubroutine package provided.  I chose to convert the subroutine to a
  401. Xstandalone program that is self-contained and can be executed from other
  402. Xprograms.
  403. X
  404. XVery soon afterwards I realized that the standalone program could be 
  405. Xrefitted to work as a library.  So checkpasswd comes in two forms:
  406. Xstandalone and library.
  407. X
  408. XThe standalone program reads from standard input and reports
  409. Xto standard output.
  410. X
  411. XThe library version can be called from a program and will return
  412. X1 or 0 depending on whether the password should be used.  It will
  413. Xalso print a diagnostic message to stdout if it does not deem the password
  414. Xto be desirable.
  415. X
  416. XThe routine setcheckpasswd() can be used to set options when using the library.
  417. XSee checkpasswd(3) about setting options.
  418. X
  419. X* Dictionaries...
  420. X
  421. XI figured that why no password checker to date did dictionary lookups is 
  422. Xbecause of the overhead in doing so.  I use a DBM database version of
  423. Xa dictionary to make the lookups fast.  On my system, the DBM version of
  424. X/usr/dict/words takes up only about 800K - worth the space to make
  425. Xpassword checking fast.
  426. X
  427. XSince multiple dictionaries can be searched, any new words that you want
  428. Xto 'take out' of use can be added to one of them.  As an aside, this
  429. Xis why I just couldn't use the vanilla V7 dbm - it can not handle more
  430. Xthan one database.
  431. X
  432. XThe reason for the dictionary checking is to thwart crackers who go through
  433. Xthe system dictionary looking for passwords.  If we prevent people from
  434. Xchoosing passwords which are in publicly readable dictionaries, that
  435. Xcracking strategy should then fail.
  436. X
  437. XPasswords which use punctuation or control characters or have multiple 
  438. Xchanges of case are not looked up.  Since these would not be found in the
  439. Xspelling dictionary, there is no reason to look for them.
  440. X
  441. X*** Important note ***
  442. XIt is a good idea to have dictionary data bases because if egrep(1)
  443. Xhas to be used to search an ASCII file, its arguments are prey to being
  444. Xread with ps(1).
  445. X
  446. X* How to use checkpasswd...
  447. X
  448. XThe standalone checkpasswd is not designed to be an end-user program.
  449. XIt should be installed in a library directory somewhere and used from
  450. Xanother program.  The -o, -s and -e options are provided to facilitate
  451. Xsuch use, and the 'call_ckpasswd.c' module included shows an example of this.
  452. X
  453. X* To do...
  454. X
  455. XProvide some way to pass an encrypted password on the command line.
  456. XClose the window of vunerablity on egrep argument sniffing.
  457. X
  458. X* Administrativa...
  459. X
  460. XI have done considerable testing but do not guarantee that this program
  461. Xis bullet-proof.
  462. X
  463. XThere is no copyright on this version of checkpasswd. 
  464. X
  465. X* Building checkpasswd...
  466. X
  467. X1. Edit Makefile and select which DBM method to use and where to install
  468. X   checkpasswd and config file
  469. X2. Edit dict/Makefile and select which DBM method to use.
  470. X3. Do a 'make all'.
  471. X4. Use dict/makedict to build DBM dictionaries.
  472. X
  473. X** If you are not using the standalone application version, stop now **
  474. X
  475. X5. Edit checkpasswd.cf to reflect your preferences.
  476. X6. Do a 'make install'.
  477. X
  478. X> Bugs & enhancements...
  479. X
  480. XSend bug report & enhancements to:
  481. X    clyde@emx.utexas.edu
  482. X        or
  483. X    uunet!cs.utexas.edu!ut-emx!clyde
  484. X
  485. END_OF_FILE
  486. if test 6143 -ne `wc -c <'checkpasswd/README'`; then
  487.     echo shar: \"'checkpasswd/README'\" unpacked with wrong size!
  488. fi
  489. # end of 'checkpasswd/README'
  490. fi
  491. if test -f 'checkpasswd/checkpasswd.8' -a "${1}" != "-c" ; then 
  492.   echo shar: Will not clobber existing file \"'checkpasswd/checkpasswd.8'\"
  493. else
  494. echo shar: Extracting \"'checkpasswd/checkpasswd.8'\" \(5705 characters\)
  495. sed "s/^X//" >'checkpasswd/checkpasswd.8' <<'END_OF_FILE'
  496. X'\"
  497. X'\"    @(#)checkpasswd.8    1.1 5/18/89 (cc.utexas.edu) /home/emx/u2/cc/clyde/src/new/passwd/checkpasswd/SCCS/s.checkpasswd.8
  498. X'\"
  499. X.TH CHECKPASSWD 8
  500. X.SH NAME
  501. Xcheckpasswd \- check passwords
  502. X.SH SYNOPSIS
  503. X.B checkpasswd
  504. X[
  505. X.B \-\^c
  506. Xconfig_file ] [
  507. X.B \-\^e
  508. X] [
  509. X.B \-\^o
  510. X] [
  511. X.B \-\^s
  512. X] [
  513. X.B \-\^u
  514. Xuser id ] [
  515. X.B \-\^V
  516. X] [
  517. X.B \-\^?
  518. X]
  519. X.SH DESCRIPTION
  520. X.I Checkpasswd
  521. Xreads passwords from the standard input and subjects them to suitablity tests.
  522. XThese tests are detailed below.
  523. XIt purposely does
  524. X.B not
  525. Xtake a password argument since process
  526. Xarguments are not secure on most UNIX systems - they can be printed by 
  527. X.IR ps (1).
  528. X.PP
  529. X.I Checkpasswd
  530. Xis not meant as a replacement for 
  531. X.IR passwd (1)
  532. Xand so does not suppress character echo while reading passwords.
  533. XThe standard input can be redirected from a pipe or file, which facilitates
  534. Xit being called from another program such as 
  535. X.IR passwd (1).
  536. X.PP
  537. X.I Checkpasswd
  538. Xexits with the check status for the most recent password entered.
  539. XThe check status is one of the following:
  540. X.nf
  541. X-1    A serious error occured.
  542. X0    The password passed all checks.
  543. X1    The password was a null string.
  544. X2    The password was \'too easy\' to guess.
  545. X3    The password was part of the users \'\fIfinger\fP\' information.
  546. X4    The password was found in a dictionary search.
  547. X5    The password contained an illegal character or sequence.
  548. X6    The password was too short.
  549. X.fi
  550. X.PP
  551. XIn addition to the check status, an informative message is printed to
  552. Xthe standard output (unless supressed).
  553. X.PP
  554. XThe options available are:
  555. X.TP
  556. X.B \-\^c
  557. XTake the next argument as the configuration file to read.
  558. XSee the CONFIGURATION section for information on the configuration file.
  559. X.TP
  560. X.B \-\^e
  561. XPrint the check status in numeric form as well as the informative message.
  562. XThis is useful for programs which want to process the output from
  563. X.IR checkpasswd .
  564. X.TP
  565. X.B \-\^o
  566. XTest one password only and exit with check status.
  567. X.TP
  568. X.B \-\^s
  569. XSupress output of informative messages.
  570. X.TP
  571. X.B \-\^u
  572. XTake the next argument as the user id (in numeric form) to use for password
  573. Xfile data checking.
  574. XThis option is restricted to the super-user.
  575. X.TP
  576. X.B \-\^V
  577. XPrint version information.
  578. X.TP
  579. X.B \-\^?
  580. XPrint help message.
  581. X.SH "CHECKS PERFORMED"
  582. X.PP
  583. X.I Checkpasswd
  584. Xperforms the following tests on each password:
  585. X.IP 1.
  586. XLength is checked to see if it is within bounds.
  587. XPasswords which are too short are rejected, while passwords that are
  588. X\'too long\' are passed but a warning message issued.
  589. X.IP 2.
  590. XThe password is scanned for illegal characters and character combinations,
  591. Xsuch as runs of the same character (e.g. \'aaa\').
  592. XPasswords that do not use upper and lower case alphabetics will be rejected
  593. Xunless otherwise specified via the configuration file.
  594. X.IP 3.
  595. XThe password is checked against various per-site information, such as the
  596. Xhost name.
  597. X.IP 4.
  598. XThe password is checked against the
  599. X.IR passwd (5)
  600. Xinformation for the user.
  601. XThe password is compared against a number of permutations of this data.
  602. X.IP 5.
  603. XThe password is checked against the user\'s \'finger\' information.
  604. X.IP 6.
  605. XThe password is search for in a list of dictionaries and is rejected if it
  606. Xis found in any of them.
  607. X.SH CONFIGURATION
  608. X.PP
  609. XMany of the criteria used by
  610. X.I checkpasswd
  611. Xcan be set via a configuration file.
  612. XThe default configuration file is 
  613. X.BR /usr/adm/checkpasswd.cf .
  614. X.PP
  615. XLines in the configuration file which start with '#' ignored.
  616. XItems in \'[ ]\' are optional, the default value is in \'{ }\'.
  617. X.sp
  618. X.nf
  619. X\fBdictionary\fP    /path/to/dictionary    [description of dictionary]
  620. X.fi
  621. X.RS
  622. XSpecifies a dictionary to look in.
  623. XIf there is a DBM data base version of the dictionary, that will be used,
  624. Xelse 
  625. X.IR egrep
  626. Xwill be used on the file.
  627. XThere may be multiple
  628. X.B dictionary
  629. Xdirectives in the configuration file, and they will be searched in
  630. Xthe order given.
  631. XA list of default dictionaries may be built into
  632. X.IR checkpasswd .
  633. X.br
  634. XThe
  635. X.I makedict
  636. Xprogram can be used to build DBM dictionaries, and
  637. X.I viewdict
  638. Xused to review them.
  639. X.RE
  640. X.sp
  641. X.nf
  642. X\fBsinglecase\fP    yes | {no}
  643. X.fi
  644. X.RS
  645. XSpecifies whether or not single-case passwords are legal.
  646. XThe default is to reject single-case passwords.
  647. X.RE
  648. X.sp
  649. X.nf
  650. X\fBminlength\fP    N {5}    
  651. X.fi
  652. X.RS
  653. XSpecifies the minimum password length to accept.
  654. XPasswords shorter that this will not be accepted.
  655. X.RE
  656. X.sp
  657. X.nf
  658. X\fBmaxlength\fP    N {8}
  659. X.fi
  660. X.RS
  661. XSpecifies the maximum effective password length.
  662. XThis does not affect acceptance of a password.
  663. XThe 
  664. X.IR crypt (3)
  665. Xroutine usually encrypts only the first 8 characters of a string due to the
  666. Xalgorithim used.
  667. XIf the password is longer than this, a warning message is printed to inform the
  668. Xuser that not the entire password will be used.
  669. X.RE
  670. X.sp
  671. X.nf
  672. X\fBprintonly\fP    yes | {no}
  673. X.fi
  674. X.RS
  675. XSpecifies that only printable ASCII characters are to be allowed in passwords.
  676. XUse of control characters should be encouraged but may cause problems on
  677. Xsome systems.
  678. X.RE
  679. X.sp
  680. X.nf
  681. X\fBbadchars\fP    "list-of-characters"
  682. X.br
  683. X\fBbadchars\fP    +"list-of-characters"
  684. X.fi
  685. X.RS
  686. XSpecifies a list of characters which are not allowed in passwords.
  687. XThe first form replaces the illegal character table.
  688. XThe second form adds to the illegal character table.
  689. XCharacters may be given in the following forms:
  690. X.br
  691. X.ti +.25i
  692. XC special character escapes (e.g. \\\|t )
  693. X.br
  694. X.ti +.25i
  695. X\\\|0[0\-7] (e.g. \\\|013)
  696. X.br
  697. X.ti +.25i
  698. X\\\|0x[0\-9\| \|a\-f] (e.g. \\\|0xa)
  699. X.br
  700. X.ti +.25i
  701. X^[a\-z\|,\|A\-Z\|,\|[\|,\|\\\|,\|]\|,\|^\|,\|-] (e.g.,^c)
  702. X.br
  703. X.sp
  704. XThe default content of the illegal characters table is a collection
  705. Xof terminal special characters.
  706. X.RE
  707. X.in 0
  708. X.SH SEE ALSO
  709. Xmakedict, viewdict, passwd(1)
  710. X.SH AUTHOR
  711. XClyde Hoover
  712. X.br
  713. XComputation Center
  714. X.br
  715. XThe University of Texas at Austin
  716. X.br
  717. XAustin, Texas
  718. X.br
  719. Xclyde@emx.utexas.edu, uunet!cs.utexas.edu!ut-emx!clyde
  720. X.PP
  721. END_OF_FILE
  722. if test 5705 -ne `wc -c <'checkpasswd/checkpasswd.8'`; then
  723.     echo shar: \"'checkpasswd/checkpasswd.8'\" unpacked with wrong size!
  724. fi
  725. # end of 'checkpasswd/checkpasswd.8'
  726. fi
  727. if test -f 'checkpasswd/pwck_dict.c' -a "${1}" != "-c" ; then 
  728.   echo shar: Will not clobber existing file \"'checkpasswd/pwck_dict.c'\"
  729. else
  730. echo shar: Extracting \"'checkpasswd/pwck_dict.c'\" \(4785 characters\)
  731. sed "s/^X//" >'checkpasswd/pwck_dict.c' <<'END_OF_FILE'
  732. X
  733. X/* --------------------------------------------------------------------  */
  734. X/*                                                                       */
  735. X/*                         Author: Clyde Hoover                          */
  736. X/*                          Computation Center                           */
  737. X/*                   The University of Texas at Austin                   */
  738. X/*                          Austin, Texas 78712                          */
  739. X/*                         clyde@emx.utexas.edu                          */
  740. X/*                   uunet!cs.utexas.edu!ut-emx!clyde                    */
  741. X/*                                                                       */
  742. X/*This code may be distributed freely, provided this notice is retained. */
  743. X/*                                                                       */
  744. X/* --------------------------------------------------------------------  */
  745. X/*
  746. X *    pwck_dictionary - Look in the forbidden password dictionaries.
  747. X *    Returns:
  748. X *        PWCK_INDICT if <password> was in any dictionary
  749. X *        PWCK_OK if not
  750. X */
  751. X
  752. X#ifndef lint
  753. Xstatic char sccsid[] = "@(#)pwck_dict.c    1.2 11/26/90 (cc.utexas.edu)";
  754. X#endif
  755. X
  756. X#include "checkpasswd.h"
  757. X
  758. Xdictionary    *dictionaries = 0;    /* List of dictionaries */
  759. Xstatic char    *egrep = "PATH=/bin:/usr/bin:/usr/ucb; egrep -s"; /* egrep */
  760. X
  761. Xpwck_dictionary(password, userid, mesgbuf)
  762. Xchar    *password;    /* Password to check */
  763. Xint    userid;        /* NOTUSED */
  764. Xchar    *mesgbuf;    /* Message buffer */
  765. X{
  766. X    int    rcode;        /* Return code temp */
  767. X    char    *p;        /* Scratch */
  768. X    dictionary *d;        /* Current dictionary */
  769. X
  770. X    /*
  771. X     * If there are any non-alpha characters 
  772. X     * don't bother with the dictionary checks.
  773. X     */
  774. X    for (p = password; *p; p++) {
  775. X        if (!isalpha(*p))
  776. X            return(PWCK_OK);
  777. X    }
  778. X#ifdef    DEBUG
  779. X    printf("pwck_dictionary: \"%s\"\n", password);
  780. X#endif
  781. X    for (d = dictionaries; d; d = d->dict_next) {
  782. X#ifdef    DEBUG
  783. X        printf("\tdictionary '%s'\n", d->dict_path);
  784. X#endif
  785. X        if ((rcode = InDictionary(d->dict_path, password)) != PWCK_OK){
  786. X            (void) sprintf(mesgbuf,
  787. X                "Password found in dictionary '%s'",
  788. X                d->dict_path);
  789. X            return(rcode);
  790. X        }
  791. X    }
  792. X    return(PWCK_OK);
  793. X}
  794. X
  795. X#ifdef  MDBM
  796. X/*
  797. X *    Use the 'mdbm' package by Chris Torek and others
  798. X */
  799. X#include "mdbm.h"
  800. X#define    DBM        struct mdbm
  801. X#define    DBM_FETCH    mdbm_fetch
  802. X#define    DBM_CLOSE    mdbm_close
  803. X#endif
  804. X
  805. X/*
  806. X *    Using the 4.3BSD 'ndbm' routines
  807. X */
  808. X#ifdef  NDBM
  809. X#include <ndbm.h>
  810. X#define DBM_FETCH    dbm_fetch
  811. X#define DBM_CLOSE    dbm_close
  812. X#endif
  813. X
  814. X/*
  815. X *    InDictionary - look for <password> in <dictionary>
  816. X *
  817. X *    Look in a DBM version of the dictionary if present, 
  818. X *    else use egrep to search the flat file.
  819. X *
  820. X *    Look for <password>, then if the first letter
  821. X *    is capitalized, force to lower and look again.  I don't care
  822. X *    if <password> is in the dictionary but has mixed case letters.
  823. X *    BUT if the first letter has been capitalized, I care because
  824. X *    that's not a sufficent permutation to be secure.
  825. X *
  826. X *    If more than the first letter is capitalized, then the dictionary
  827. X *    lookup will fail.
  828. X *
  829. X *    Returns:
  830. X *        PWCK_INDICT if <password> was found in <dictionary>
  831. X *        PWCK_OK if not
  832. X */
  833. Xstatic
  834. XInDictionary(which_dictionary, password)
  835. Xchar    *which_dictionary,        /* Pathname of dictionary */
  836. X    *password;        /* Plaintext of password */
  837. X{
  838. X#if    defined(NDBM) || defined(MDBM)
  839. X    DBM    *dbp;        /* DBM database pointer */
  840. X    datum    k,        /* DBM lookup key */
  841. X        d;        /* DBM lookup datum */
  842. X#endif
  843. X    int    uc = isupper(password[0]);    /* Is first char UC? */
  844. X    char    pwtemp[BUFSIZ];            /* Scratch buffer */
  845. X#ifdef    MDBM
  846. X    if ((dbp = mdbm_open(which_dictionary, 0, 0,
  847. X        (int *)0, (int *)0, (char *)0)) == (DBM *)0)
  848. X#endif
  849. X#ifdef    NDBM
  850. X    if ((dbp = dbm_open(which_dictionary, 0, 0)) == (DBM *)0)
  851. X#endif
  852. X    {
  853. X        char    command[BUFSIZ];    /* Command build buffer */
  854. X        int    rc;            /* Return code from sytem(3) */
  855. X
  856. X        if ((rc = open(which_dictionary, 0)) < 0)
  857. X            return(PWCK_OK);
  858. X        (void) close(rc);
  859. X        /*
  860. X         * If the first letter is capitalized, look for
  861. X         * "[wW]ord" else look for "word"
  862. X         */
  863. X        if (uc) 
  864. X            (void) sprintf(command,
  865. X                "%s '^[%c%c]%s$' %s > /dev/null",
  866. X                egrep, password[0], password[0] | 040,
  867. X                &password[1], which_dictionary);
  868. X        else
  869. X            (void) sprintf(command, "%s '^%s$' %s > /dev/null",
  870. X                egrep, password, which_dictionary);
  871. X        rc = system(command);
  872. X        if (rc == 0) 
  873. X            return(PWCK_INDICT);
  874. X        else
  875. X            return(PWCK_OK);
  876. X    } 
  877. X#if    defined(NDBM) || defined(MDBM)
  878. X#define    returnwith(code) { DBM_CLOSE(dbp); return(code); }
  879. X    /*
  880. X     * Look in the DBM version of the dictionary.
  881. X     */
  882. X    (void) strcpy(pwtemp, password);
  883. X    k.dptr = pwtemp;
  884. X    k.dsize = strlen(pwtemp);
  885. X    d = DBM_FETCH(dbp, k);
  886. X    if (d.dptr)
  887. X        returnwith(PWCK_INDICT);
  888. X    if (uc) {
  889. X        pwtemp[0] |= 040;
  890. X        d = DBM_FETCH(dbp, k);
  891. X        if (d.dptr)
  892. X            returnwith(PWCK_INDICT);
  893. X    }
  894. X    returnwith(PWCK_OK);
  895. X#endif    /* defined(NDBM) || defined(MDBM) */
  896. X}
  897. X/*    End pwck_dict.c */
  898. END_OF_FILE
  899. if test 4785 -ne `wc -c <'checkpasswd/pwck_dict.c'`; then
  900.     echo shar: \"'checkpasswd/pwck_dict.c'\" unpacked with wrong size!
  901. fi
  902. # end of 'checkpasswd/pwck_dict.c'
  903. fi
  904. if test -f 'checkpasswd/pwck_lexical.c' -a "${1}" != "-c" ; then 
  905.   echo shar: Will not clobber existing file \"'checkpasswd/pwck_lexical.c'\"
  906. else
  907. echo shar: Extracting \"'checkpasswd/pwck_lexical.c'\" \(4195 characters\)
  908. sed "s/^X//" >'checkpasswd/pwck_lexical.c' <<'END_OF_FILE'
  909. X
  910. X/* --------------------------------------------------------------------  */
  911. X/*                                                                       */
  912. X/*                         Author: Clyde Hoover                          */
  913. X/*                          Computation Center                           */
  914. X/*                   The University of Texas at Austin                   */
  915. X/*                          Austin, Texas 78712                          */
  916. X/*                         clyde@emx.utexas.edu                          */
  917. X/*                   uunet!cs.utexas.edu!ut-emx!clyde                    */
  918. X/*                                                                       */
  919. X/*This code may be distributed freely, provided this notice is retained. */
  920. X/*                                                                       */
  921. X/* --------------------------------------------------------------------  */
  922. X/*
  923. X *    pwck_lexical - Perform lexical analysis of password candidate.
  924. X *
  925. X *    Things which are ok:
  926. X *        Mixed case
  927. X *        Digits
  928. X *        Punctutation
  929. X *        Control characters (except for those in the forbidden table)
  930. X *
  931. X *    Things which are NOT ok:
  932. X *        Passwords less than 'min_length' characters
  933. X *        Runs of more than <run_length> of the same character
  934. X *            (e.g. 'zzz')
  935. X *        Single-case strings (selectable via the config file)
  936. X *
  937. X *    Things NOT checked for:
  938. X *        Cycles of character groups (e.g. 'aabbcc' or 'ababab')
  939. X *        Sequential characters 'abcdef' or '123456'
  940. X */
  941. X
  942. X#ifndef lint
  943. Xstatic char sccsid[] = "@(#)pwck_lexical.c    1.3 11/7/89 (cc.utexas.edu)";
  944. X#endif
  945. X
  946. X#include "checkpasswd.h"
  947. X
  948. X#define    P_U    0x1     /* Upper case in password */
  949. X#define    P_L    0x2     /* Lower case in password */
  950. X#define    P_C    0x4     /* Control chars in password */
  951. X#define    P_D    0x8     /* Digits in password */
  952. X#define    P_P    0x10     /* Punctutation chars in password */
  953. X
  954. X#define    hasone(P)    (what |= (P))
  955. X#define    hasany(P)    ((what & (P)) == (P))
  956. X
  957. Xpwck_lexical(password, userid, mesg)
  958. Xchar    *password;        /* Password to check */
  959. Xint    userid;            /* NOTUSED */
  960. Xchar    *mesg;        /* Message buffer */
  961. X{
  962. X    int    rc;        /* Duplicate character run count */
  963. X    char    *p = password;    /* Scratch */
  964. X    char    what = 0,    /* Lexical analysis result flags */
  965. X        last = 0;    /* Last character seen (for run checks) */
  966. X
  967. X    mesg[0] = 0;
  968. X#ifdef    DEBUG
  969. X    printf("pwck_lexical: \"%s\"\n", password);
  970. X#endif
  971. X    rc = strlen(password);
  972. X    if (min_length && rc < min_length)
  973. X        return(PWCK_SHORT);
  974. X    /*
  975. X     * Only the first <max_length> characters of a password are actually
  976. X     * used due to the limitations of crypt(3).  If the given
  977. X     * password is longer than this, issue warning message.
  978. X     */
  979. X    if (max_length && rc > max_length) {
  980. X        printf("WARNING: Only the first %d characters of this password will be used \n",
  981. X            max_length);
  982. X    }
  983. X
  984. X    for (p = password; *p; p++) {
  985. X        if (*p != last) {
  986. X            last = *p;
  987. X            rc = 1;
  988. X        }
  989. X        else {        /* Run of same characters */
  990. X            if (run_length && ++rc >= run_length) {
  991. X                (void) sprintf(mesg,
  992. X            "This password has %d or more repeated characters",
  993. X                    run_length);
  994. X                return(PWCK_OBVIOUS);
  995. X            }
  996. X        }
  997. X        if (*p < ' ' || *p > '~') {    /* Non-printing character */
  998. X            char    *_ctran();
  999. X
  1000. X            if (print_only) {
  1001. X                (void) strcpy(mesg,
  1002. X            "This password has non-printing characters");
  1003. X                return(PWCK_ILLCHAR);
  1004. X            }
  1005. X            if (index(illegalcc, *p)) {
  1006. X                (void) sprintf(mesg,
  1007. X                "Illegal character '%s' in this password",
  1008. X                    _ctran(*p));
  1009. X                return(PWCK_ILLCHAR);
  1010. X            }
  1011. X            hasone(P_C);
  1012. X        }
  1013. X        else if (isupper(*p))    hasone(P_U);
  1014. X        else if (islower(*p))    hasone(P_L);
  1015. X        else if (ispunct(*p))    hasone(P_P);
  1016. X        else if (isdigit(*p))    hasone(P_D);
  1017. X    }
  1018. X    if (hasany(P_U | P_L))    return(PWCK_OK);    /* UC+lc */
  1019. X    if (hasany(P_D))    return(PWCK_OK);    /* Numbers */
  1020. X    if (hasany(P_P))    return(PWCK_OK);    /* Punctutation chars */
  1021. X    if (hasany(P_C))    return(PWCK_OK);    /* Control chars */
  1022. X    /*
  1023. X     *    Check for mono-case passwords 
  1024. X     */
  1025. X    if (!hasany(P_U) && single_case)    /* All lower case alpha */
  1026. X        return(PWCK_OK);
  1027. X    if (!hasany(P_L) && single_case)    /* All upper case alpha */
  1028. X        return(PWCK_OK);
  1029. X
  1030. X    if (!hasany(P_L))
  1031. X        (void) strcpy(mesg,
  1032. X            "Upper-case only passwords not allowed");
  1033. X    if (!hasany(P_U))
  1034. X        (void) strcpy(mesg,
  1035. X            "Lower-case only passwords not allowed");
  1036. X    return(PWCK_ILLCHAR);
  1037. X}
  1038. X/*    End pwck_lexical.c */
  1039. END_OF_FILE
  1040. if test 4195 -ne `wc -c <'checkpasswd/pwck_lexical.c'`; then
  1041.     echo shar: \"'checkpasswd/pwck_lexical.c'\" unpacked with wrong size!
  1042. fi
  1043. # end of 'checkpasswd/pwck_lexical.c'
  1044. fi
  1045. if test -f 'pw_passwd.c' -a "${1}" != "-c" ; then 
  1046.   echo shar: Will not clobber existing file \"'pw_passwd.c'\"
  1047. else
  1048. echo shar: Extracting \"'pw_passwd.c'\" \(9416 characters\)
  1049. sed "s/^X//" >'pw_passwd.c' <<'END_OF_FILE'
  1050. X
  1051. X/* --------------------------------------------------------------------  */
  1052. X/*                                                                       */
  1053. X/*                         Author: Clyde Hoover                          */
  1054. X/*                          Computation Center                           */
  1055. X/*                   The University of Texas at Austin                   */
  1056. X/*                          Austin, Texas 78712                          */
  1057. X/*                         clyde@emx.utexas.edu                          */
  1058. X/*                   uunet!cs.utexas.edu!ut-emx!clyde                    */
  1059. X/*                                                                       */
  1060. X/*This code may be distributed freely, provided this notice is retained. */
  1061. X/*                                                                       */
  1062. X/* --------------------------------------------------------------------  */
  1063. X/*
  1064. X *    pw_passwd - Routines for dealing with password files.
  1065. X *        Handles V7 / *.* BSD / Sys V format.
  1066. X */
  1067. X#include <stdio.h>
  1068. X#include <sys/types.h>
  1069. X#include <sys/stat.h>
  1070. X#include <signal.h>
  1071. X#include <errno.h>
  1072. X#include <pwd.h>
  1073. X#include <fcntl.h>
  1074. X
  1075. X#ifndef lint
  1076. Xstatic char sccsid[] = "@(#)pw_passwd.c    1.9 1/24/91 (cc.utexas.edu)";
  1077. X#endif
  1078. X
  1079. X#define    SLOP    128    /*  Size difference tolerated old <> new passwd file */
  1080. X
  1081. X#ifdef    SYSV
  1082. X/*
  1083. X *    System V password aging stuff
  1084. X */
  1085. X#define    SEC_PER_WEEK    ((long )24 * 7 * 60 * 60)
  1086. X
  1087. Xextern long a64l();
  1088. Xextern char *l64a();
  1089. X
  1090. Xstatic time_t    pwage = 0,
  1091. X        maxpwtime = 0,
  1092. X        minpwtime = 0,
  1093. X        now;
  1094. X#endif
  1095. X
  1096. Xtypedef struct passwd    passwd;
  1097. Xtypedef    struct passwd    *passwdp;
  1098. X
  1099. Xstatic passwd    theUser,        /* The user to change */
  1100. X        Me;            /* The user who invoked command */
  1101. Xstatic int    myuid,            /* Uid of program */
  1102. X        mytempfile = 0;        /* Does PASSWD_TEMP belong to me? */
  1103. X
  1104. X/*
  1105. X *    File names
  1106. X */
  1107. X#ifndef    PASSWD_FILE
  1108. X#define    PASSWD_FILE    "/etc/passwd"
  1109. X#endif
  1110. X
  1111. X#ifndef    PASSWD_SAVE
  1112. X#define    PASSWD_SAVE    "/etc/opasswd"
  1113. X#endif
  1114. X
  1115. X#ifndef    PASSWD_TEMP
  1116. X#define    PASSWD_TEMP    "/etc/ptmp"
  1117. X#endif
  1118. X
  1119. X#define    PASSWD_MODE    0644
  1120. X
  1121. X#ifdef    DEBUG
  1122. Xstatic char    *passwdtemp = "./etc_ptmp",
  1123. X        *passwdfile = "./etc_passwd",
  1124. X        *savefile = "./etc_opasswd";
  1125. X#else
  1126. Xstatic char    *passwdtemp = PASSWD_TEMP,
  1127. X        *passwdfile = PASSWD_FILE,
  1128. X        *savefile = PASSWD_SAVE;
  1129. X#endif
  1130. X
  1131. Xextern int    errno;
  1132. X
  1133. Xchar    *getlogin(),
  1134. X    *crypt();
  1135. X
  1136. X/*
  1137. X *    pw_initialize - set up
  1138. X */
  1139. Xpw_initialize()
  1140. X{
  1141. X    passwdp    me;    /* Temp */
  1142. X    char    *myname = getlogin();    /* Name of invoker */
  1143. X
  1144. X#ifdef    DEBUG
  1145. X    setpwfile(passwdfile);
  1146. X#endif
  1147. X    myuid = getuid();
  1148. X    if (myname && *myname) {
  1149. X        if ((me = getpwnam(myname)) == NULL)
  1150. X            quit(1, "Cannot get user identification from name.\n");
  1151. X    } else {
  1152. X        if ((me = getpwuid(myuid)) == NULL)
  1153. X            quit(1, "Cannot get user identification from uid.\n");
  1154. X    }
  1155. X
  1156. X    _cppasswd(me, &Me);
  1157. X    return(1);
  1158. X}
  1159. X
  1160. X/*
  1161. X *    pw_getuserbyname - get password 
  1162. X *
  1163. X *    Returns 1 if passwd info found for <name>
  1164. X *        0 otherwise
  1165. X */
  1166. Xpw_getuserbyname(name, passwdb)
  1167. Xchar    *name,        /* User name */
  1168. X    *passwdb;    /* Where to stuff password */
  1169. X{
  1170. X    passwdp    p;    /* Temp */
  1171. X
  1172. X    if ((p = getpwnam(name)) == NULL)
  1173. X        return(0);
  1174. X    _cppasswd(p, &theUser);
  1175. X    (void) strcpy(passwdb, p->pw_passwd);
  1176. X    return(1);
  1177. X}
  1178. X
  1179. X/*
  1180. X *    pw_permission - check password change permission
  1181. X *
  1182. X *    Returns 1 if password can be changed
  1183. X *        0 if not
  1184. X */
  1185. Xpw_permission()
  1186. X{
  1187. X    if (strcmp(Me.pw_name, theUser.pw_name) && myuid)
  1188. X        return(0);
  1189. X
  1190. X    /*
  1191. X     * Other checks can be put here to determine if the invoker should
  1192. X     * be allowed to change this password.
  1193. X     */
  1194. X#ifdef    SYSV
  1195. X    if (theUser.pw_age) {
  1196. X        pwage = a64l(theUser.pw_age);
  1197. X        maxpwtime = pwage & 077;
  1198. X        minpwtime = (pwage >> 6) & 077;
  1199. X        pwage >>= 12;
  1200. X        (void) time(&now);
  1201. X        now /= SEC_PER_WEEK;
  1202. X        if (pwage <= now) {
  1203. X            if (myuid && (now < (pwage + minpwtime))) {
  1204. X                fprintf(stderr, 
  1205. X                     "Sorry: < %ld  weeks since last change\n",
  1206. X                     minpwtime);
  1207. X                return(0);
  1208. X            }
  1209. X            if ((minpwtime > maxpwtime) && myuid) {
  1210. X                fprintf(stderr,
  1211. X                    "You may not change this password.\n");
  1212. X                return(0);
  1213. X            }
  1214. X        }
  1215. X    }
  1216. X#endif
  1217. X    return(1);
  1218. X}
  1219. X
  1220. X/*
  1221. X *    pw_compare - compare old and new passwords
  1222. X *
  1223. X *    Returns 1 if check = new, 0 if not
  1224. X */
  1225. Xpw_compare(current, check)
  1226. Xchar    *current,        /* Current pw (encrypted) */
  1227. X    *check;            /* check pw (plain) */
  1228. X{
  1229. X    if (!*current)
  1230. X        return(0);
  1231. X    return(!strcmp(current, crypt(check, current)));
  1232. X}
  1233. X
  1234. X/*
  1235. X *    pw_check - sanity check password.  Right now just calls
  1236. X *        the password check program
  1237. X *
  1238. X *    Returns 1 if password is ok to use, 0 otherwise
  1239. X */
  1240. Xpw_check(newpw)
  1241. Xchar    *newpw;        /* New password (plain) */
  1242. X{
  1243. X    /* Put other administrative checks here */
  1244. X    return(checkpasswd(theUser.pw_uid, newpw));
  1245. X}
  1246. X
  1247. X/*
  1248. X *    pw_replace - replace password in passwd file 
  1249. X */
  1250. Xpw_replace(newpwd, curpwd)
  1251. Xchar    *newpwd,        /* New password (plain) */
  1252. X    *curpwd;        /* Old password (plain) */
  1253. X{
  1254. X#ifdef    SYSV
  1255. X    int    (*sigint)(),        /* Save SIGINT */
  1256. X        (*sigquit)();        /* Save SIGQUIT */
  1257. X#else
  1258. X    long    oldsigs,        /* Signal mask save */
  1259. X        blocksigs = sigmask(SIGINT) |    /* Signals to block */
  1260. X                sigmask(SIGQUIT) |    /* while updating */
  1261. X                sigmask(SIGTSTP);    /* password file */
  1262. X#endif
  1263. X    passwdp px;        /* Temp */
  1264. X    char    salt[4];    /* Encryption salt */
  1265. X    FILE    *tf;        /* File ptr to new passwd file */
  1266. X    int    fd;        /* File desc. to new passwd file */
  1267. X    struct stat    oldstat,    /* Stat of current passwd file */
  1268. X            newstat;    /* Stat of new passwd file */
  1269. X
  1270. X    /*
  1271. X     * Prepare password entry 'theUser' for replacement
  1272. X     */
  1273. X    randomstring(salt, sizeof(salt));
  1274. X    theUser.pw_passwd = crypt(newpwd, salt);
  1275. X#ifdef    SYSV
  1276. X    /*
  1277. X     * Update password age field
  1278. X     */
  1279. X    if (theUser.pw_age) {
  1280. X        if (maxpwtime == 0)
  1281. X            *theUser.pw_age = '\0';
  1282. X        else {
  1283. X            now = time((time_t *)0) / SEC_PER_WEEK;
  1284. X            pwage = maxpwtime
  1285. X                + (minpwtime << 6)
  1286. X                + (now << 12);
  1287. X            theUser.pw_age = l64a(pwage);
  1288. X        }
  1289. X    }
  1290. X#endif
  1291. X    (void) umask(0);
  1292. X    (void) stat(passwdfile, &oldstat);
  1293. X    fd = open(passwdtemp, O_WRONLY|O_CREAT|O_EXCL, PASSWD_MODE);
  1294. X    if (fd < 0) {
  1295. X        if (errno == EEXIST)
  1296. X            quit(0, "Password file busy - try again.\n");
  1297. X        perror("Tempfile create");
  1298. X        quit(1, "Cannot create temp file.\n");
  1299. X    }
  1300. X    mytempfile = 1;
  1301. X    if ((tf = fdopen(fd, "w")) == NULL)
  1302. X        quit(1, "Cannot fdopen temp file.");
  1303. X#ifdef    SYSV
  1304. X    sigint = signal(SIGINT, SIG_IGN);
  1305. X    sigquit = signal(SIGQUIT, SIG_IGN);
  1306. X#else
  1307. X    oldsigs = sigblock(blocksigs);
  1308. X#endif
  1309. X    setpwent();
  1310. X    while ((px = getpwent()) != NULL) {
  1311. X        if (px->pw_name == 0 || px->pw_name[0] == 0) /* Sanity check */
  1312. X            continue;
  1313. X        if (strcmp(px->pw_name, theUser.pw_name) == 0)
  1314. X            px = &theUser;
  1315. X        (void) putpwent(px, tf);
  1316. X    }
  1317. X    endpwent();
  1318. X    (void) fflush(tf);
  1319. X    (void) fstat(fileno(tf), &newstat);
  1320. X    (void) fclose(tf);
  1321. X    /*
  1322. X     * Check if the new password file is complete.  Since the encrypted
  1323. X     * password is of a fixed length, the new file should be roughly
  1324. X     * the same size as the old one.
  1325. X     */
  1326. X    if (newstat.st_size < (oldstat.st_size - SLOP))
  1327. X        quit(1, "New password file appears to be incomplete - aborting.\n");
  1328. X
  1329. X    if (rename(passwdfile, savefile) < 0) {
  1330. X        perror("Password file save");
  1331. X        unlink(passwdtemp);
  1332. X        quit(1, "Can't save password file");
  1333. X    }
  1334. X    if (rename(passwdtemp, passwdfile) < 0) {
  1335. X        perror("Password file replace");
  1336. X        (void) unlink(passwdtemp);
  1337. X        (void) link(savefile, passwdfile);
  1338. X        quit(1, "Can't replace password file");
  1339. X    }
  1340. X#ifdef    BSD4_3
  1341. X    updatedbm();
  1342. X#endif
  1343. X#ifdef    SYSV
  1344. X    (void) signal(SIGINT, sigint);
  1345. X    (void) signal(SIGQUIT, sigquit);
  1346. X#else
  1347. X    (void) sigsetmask(oldsigs);
  1348. X#endif
  1349. X}
  1350. X
  1351. X/*
  1352. X *    pw_cleanup - clean up after myself
  1353. X */
  1354. Xpw_cleanup(code)
  1355. Xint    code;        /* 0 for normal, 1 for abort */ /*NOTUSED*/
  1356. X{
  1357. X    if (mytempfile)
  1358. X        (void) unlink(passwdtemp);
  1359. X}
  1360. X
  1361. X/*
  1362. X *    _newstr - copy string into new storage
  1363. X */
  1364. Xstatic char *
  1365. X_newstr(s)
  1366. Xchar    *s;        /* String to copy */
  1367. X{
  1368. X    register char    *t;    /* Temp */
  1369. X    char    *malloc();
  1370. X
  1371. X    if (s == NULL)
  1372. X        return(0);
  1373. X    t = malloc(strlen(s) + 1);
  1374. X    if (t == NULL)
  1375. X        quit(1, "No memory.\n");
  1376. X    (void) strcpy(t, s);
  1377. X    return(t);
  1378. X}
  1379. X
  1380. X/*
  1381. X *    _cppasswd - copy a passwd structure
  1382. X */
  1383. Xstatic
  1384. X_cppasswd(f,t)
  1385. Xpasswdp    f,        /* From */
  1386. X    t;        /* To */
  1387. X{
  1388. X    *t = *f;
  1389. X    t->pw_name = _newstr(f->pw_name);
  1390. X#ifdef    SYSV
  1391. X    t->pw_age = _newstr(f->pw_age);
  1392. X#endif
  1393. X    t->pw_passwd = _newstr(f->pw_passwd);
  1394. X    t->pw_comment = _newstr(f->pw_comment);
  1395. X    t->pw_gecos = _newstr(f->pw_gecos);
  1396. X    t->pw_dir = _newstr(f->pw_dir);
  1397. X    t->pw_shell = _newstr(f->pw_shell);
  1398. X}
  1399. X
  1400. X#ifdef    BSD4_3
  1401. X/*
  1402. X *    Update the hashed password data base
  1403. X */
  1404. X#include <ndbm.h>
  1405. X
  1406. X#define    SCOPY(S) xp = (S); while (*cp++ = *xp++)
  1407. X#define    BCOPY(B) bcopy((char *)&(B), cp, sizeof(int)); cp += sizeof(int)
  1408. X
  1409. Xupdatedbm()
  1410. X{
  1411. X    DBM    *pwd;        /* DBM data base passwd */
  1412. X    register char    *cp,    /* Data storage pointer */
  1413. X            *xp;    /* String copy pointer */
  1414. X    datum    key,        /* DBM key datum */
  1415. X        data;        /* DBM data store datum */
  1416. X    char    buf[512];    /* Data buffer */
  1417. X
  1418. X    pwd = dbm_open(passwdfile, O_RDWR, 0);
  1419. X    if (pwd == 0)
  1420. X        return;
  1421. X    cp = buf;
  1422. X    /* Pack passwd entry in the form expected by the getpw* routines */
  1423. X    SCOPY(theUser.pw_name);
  1424. X    SCOPY(theUser.pw_passwd);
  1425. X    BCOPY(theUser.pw_uid);
  1426. X    BCOPY(theUser.pw_gid);
  1427. X    BCOPY(theUser.pw_quota);
  1428. X    SCOPY(theUser.pw_comment);
  1429. X    SCOPY(theUser.pw_gecos);
  1430. X    SCOPY(theUser.pw_dir);
  1431. X    SCOPY(theUser.pw_shell);
  1432. X
  1433. X    data.dptr = buf;
  1434. X    data.dsize = cp - buf;
  1435. X    key.dptr = theUser.pw_name;
  1436. X    key.dsize = strlen(theUser.pw_name);
  1437. X    if (dbm_store(pwd, key, data, DBM_REPLACE) < 0) {
  1438. X        perror("dbm_store (name)");
  1439. X        quit(1, "Can't store passwd entry (name key).\n");
  1440. X    }
  1441. X    key.dptr = (char *)&theUser.pw_uid;
  1442. X    key.dsize = sizeof (int);
  1443. X    if (dbm_store(pwd, key, data, DBM_REPLACE) < 0) {
  1444. X        perror("dbm_store (uid)");
  1445. X        quit(1, "Can't store passwd entry (uid key).\n");
  1446. X    }
  1447. X    dbm_close(pwd);
  1448. X}
  1449. X#endif
  1450. END_OF_FILE
  1451. if test 9416 -ne `wc -c <'pw_passwd.c'`; then
  1452.     echo shar: \"'pw_passwd.c'\" unpacked with wrong size!
  1453. fi
  1454. # end of 'pw_passwd.c'
  1455. fi
  1456. if test -f 'pw_userinfo.c' -a "${1}" != "-c" ; then 
  1457.   echo shar: Will not clobber existing file \"'pw_userinfo.c'\"
  1458. else
  1459. echo shar: Extracting \"'pw_userinfo.c'\" \(6714 characters\)
  1460. sed "s/^X//" >'pw_userinfo.c' <<'END_OF_FILE'
  1461. X
  1462. X/* --------------------------------------------------------------------  */
  1463. X/*                                                                       */
  1464. X/*                         Author: Clyde Hoover                          */
  1465. X/*                          Computation Center                           */
  1466. X/*                   The University of Texas at Austin                   */
  1467. X/*                          Austin, Texas 78712                          */
  1468. X/*                         clyde@emx.utexas.edu                          */
  1469. X/*                   uunet!cs.utexas.edu!ut-emx!clyde                    */
  1470. X/*                                                                       */
  1471. X/*This code may be distributed freely, provided this notice is retained. */
  1472. X/*                                                                       */
  1473. X/* --------------------------------------------------------------------  */
  1474. X/*
  1475. X *    pw_userinfo.c - UTEXAS CC UNIX User Information Data Base
  1476. X *        backend for npasswd
  1477. X */
  1478. X#ifndef lint
  1479. Xstatic char sccsid[] = "@(#)pw_userinfo.c    1.4 8/7/90 (cc.utexas.edu) /tmp_mnt/usr/share/src/private/ut/share/bin/passwd/SCCS/s.pw_userinfo.c";
  1480. X#endif
  1481. X
  1482. X#include <stdio.h>
  1483. X#include <errno.h>
  1484. X#include <syslog.h>
  1485. X#include <strings.h>
  1486. X#include <signal.h>
  1487. X#include <pwd.h>
  1488. X#include <local/userinfo.h>
  1489. X
  1490. Xstatic userdata    theUser,    /* User having password changed */
  1491. X        Me;        /* User doing password change */
  1492. X
  1493. X#define    P_USER    1
  1494. X#define    P_PRIV    2
  1495. X#define    P_SU    3
  1496. X
  1497. Xstatic short    priv = P_USER;    /* Privlege level of <Me> */
  1498. X
  1499. X#define    QUOTEC    '"'        /* Character to start plaintext pwd */
  1500. X#define    XPWLEN    3        /* Length of 'original CDC password' */
  1501. X
  1502. Xextern char    *getlogin(),
  1503. X        *crypt(),
  1504. X        *index(),
  1505. X        *rindex();
  1506. X
  1507. X/*
  1508. X *    pw_initialize - set up
  1509. X */
  1510. Xpw_initialize()
  1511. X{
  1512. X    char    *myname = getlogin();        /* Login name */
  1513. X    struct passwd *pw;            /* If getlogin() fails... */
  1514. X    userptr    u;            /* Temp */
  1515. X
  1516. X    if (myname == NULL || *myname == '\0') {
  1517. X        if ((pw = getpwuid(getuid())) == ((struct passwd *)NULL))
  1518. X            quit(1, "Cannot get user name.\n");
  1519. X        else
  1520. X            myname = pw->pw_name;
  1521. X    }
  1522. X    bzero((char *)&theUser, sizeof(theUser));
  1523. X    bzero((char *)&Me, sizeof(Me));
  1524. X    if ((u = getuserbyname(myname)) == NULL)
  1525. X        quit(1, "Cannot get user identification.\n");
  1526. X    Me = *u;
  1527. X    if (Me.ui_priv.p_acct_maint)    /* Account maintenance priv? */
  1528. X        priv = P_PRIV;
  1529. X    if (getuid() == 0)        /* SuperUser? */
  1530. X        priv = P_SU;
  1531. X}
  1532. X
  1533. X/*
  1534. X *    pw_getuserbyname - Get userinfo data by name
  1535. X *
  1536. X *    Returns 1 if passwd info found for <name>
  1537. X *        0 otherwise
  1538. X */
  1539. Xpw_getuserbyname(name, passwdb)
  1540. Xchar    *name,            /* Login name */
  1541. X    *passwdb;        /* Where to stash password */
  1542. X{
  1543. X    userptr    u;            /* Temp */
  1544. X
  1545. X    if ((u = getuserbyname(name)) == NULL)
  1546. X        return(0);
  1547. X    theUser = *u;
  1548. X    (void) strcpy(passwdb, theUser.ui_password);
  1549. X    return(1);
  1550. X}
  1551. X
  1552. X/*
  1553. X *    pw_permission - check if this user can change this password
  1554. X */
  1555. Xpw_permission()
  1556. X{
  1557. X    int    mypasswd        /* Wanting to change own password? */
  1558. X        = (theUser.ui_uid == Me.ui_uid);
  1559. X
  1560. X    /*
  1561. X     * Must be su to change root password.
  1562. X     */
  1563. X    if (theUser.ui_uid == 0 && priv != P_SU) {
  1564. X        fprintf(stderr, "Permission denied.\n");
  1565. X        return(0);
  1566. X    }
  1567. X
  1568. X    /*
  1569. X     * Must be su or have 'account maintenace' capability to change
  1570. X     * someone else's password.
  1571. X     */
  1572. X    if (!mypasswd && priv < P_PRIV) {
  1573. X        fprintf(stderr, "Permission denied.\n");
  1574. X        return(0);
  1575. X    }
  1576. X
  1577. X    /*
  1578. X     * If 'password change' capability denied, then user cannot
  1579. X     * change their own password.
  1580. X     */
  1581. X    if (theUser.ui_priv.p_nopwchange && mypasswd) {
  1582. X        fprintf(stderr, "Permission denied.\n");
  1583. X        return(0);
  1584. X    }
  1585. X    /*
  1586. X     * We know at this point that the
  1587. X     * invoker does have permission to change the password.
  1588. X     */
  1589. X    return(1);
  1590. X}
  1591. X
  1592. X/*
  1593. X *    pw_compare - compare old and new passwords
  1594. X *
  1595. X *    Returns 1 if check = new, 0 if not
  1596. X */
  1597. Xpw_compare(current, check)
  1598. Xchar    *current,
  1599. X    *check;
  1600. X{
  1601. X    if (!*current)
  1602. X        return(1);
  1603. X    return(strcmp(current, crypt(check, current)) == 0);
  1604. X}
  1605. X
  1606. X/*
  1607. X *    pw_check - sanity check password.  Performs some site-specific
  1608. X *        checks, then calls the checkpasswd() code.
  1609. X *
  1610. X *    Returns 1 if password is ok to use, 0 otherwise
  1611. X */
  1612. Xpw_check(new)
  1613. Xchar    *new;        /* New password (plaintext) */
  1614. X{
  1615. X    /* Setting null password? */
  1616. X    if (strcmp(new, "@") == 0) {
  1617. X        if (theUser.ui_priv.p_null_pass == 0 || priv < P_PRIV) {
  1618. X            fprintf(stderr, "Cannot set null password.\n");
  1619. X            return(0);
  1620. X        }
  1621. X        else
  1622. X            return(1);
  1623. X    }
  1624. X
  1625. X    /* A plain text password (enclosed in ""s)? */
  1626. X    if (*new == QUOTEC) {
  1627. X        char    *p = &new[1];
  1628. X
  1629. X        while (*p) p++;
  1630. X        if (p[-1] == QUOTEC) {
  1631. X            if (priv == P_SU)    /* Reserved for superuser */
  1632. X                return(1);
  1633. X            else {
  1634. X                fprintf(stderr,
  1635. X                    "Cannot set plaintext password.\n");
  1636. X                return(0);
  1637. X            }
  1638. X        }
  1639. X    }
  1640. X
  1641. X    /* Special password (reserved for superuser) */
  1642. X    if (strlen(new) == XPWLEN && priv == P_SU)
  1643. X        return(1); 
  1644. X
  1645. X    /* Dispatch to general password checker */
  1646. X    return(checkpasswd(theUser.ui_uid, new));
  1647. X}
  1648. X
  1649. X/*
  1650. X *    pw_replace - Replace password in Userinfo database
  1651. X */
  1652. Xpw_replace(new, current)
  1653. Xchar    *new,        /* New password (plaintext) */
  1654. X    *current;    /* Current password (plaintext) [unused] */
  1655. X{
  1656. X    userptr    newu;            /* Temp */
  1657. X    int    rc;            /* Temp */
  1658. X    long    oldsigs,        /* Saved signal mask */
  1659. X        blockedsigs = sigmask(SIGINT) |        /* Signals to block */
  1660. X                  sigmask(SIGQUIT) |    /* while updating */
  1661. X                  sigmask(SIGTSTP);        /* the database */
  1662. X    extern int    errno;
  1663. X
  1664. X    /*
  1665. X     * Password has already been validated by pw_check()
  1666. X     */
  1667. X    if ((newu = getuserbyuid(theUser.ui_uid)) == NULL)
  1668. X        quit(1, "pw_replace: Cannot refetch user information.\n");
  1669. X
  1670. X    if (strcmp(new, "@") == 0) {
  1671. X        printf("Password removed from %s\n", theUser.ui_name);
  1672. X#ifndef    DEBUG
  1673. X        syslog(LOG_INFO, "Password removed from %s\n", theUser.ui_name);
  1674. X#endif
  1675. X        newu->ui_password[0] = 0;
  1676. X    }
  1677. X    else {
  1678. X        char    salt[2];
  1679. X
  1680. X        randomstring(salt, sizeof(salt));
  1681. X        (void) strcpy(newu->ui_password, crypt(new, salt));
  1682. X        if (*new == QUOTEC && priv == P_SU) {
  1683. X            char    *p = new;
  1684. X
  1685. X            while (*p) p++;
  1686. X            if (*--p == QUOTEC) {
  1687. X                *p = 0;
  1688. X                (void) strcpy(newu->ui_password, &new[1]);
  1689. X                printf("Setting plain text password.\n");
  1690. X            }
  1691. X        }
  1692. X    }
  1693. X    ui_acct(newu)->a_pwchanged = time((time_t *)0);
  1694. X
  1695. X#if    0
  1696. X    if (UIRecordChanged(newu))
  1697. X        quit(1, "Record synchronization error\n");
  1698. X#endif
  1699. X#ifdef    DEBUG
  1700. X    printf("replace %s %s\n", theUser.ui_password, newu->ui_password);
  1701. X#else
  1702. X    errno = 0;
  1703. X    oldsigs = sigblock(blockedsigs);
  1704. X    if (lockuser(theUser.ui_uid) < 0) {
  1705. X        if (errno == ETXTBSY)
  1706. X            quit(1,
  1707. X                "pw_replace: Data for %s locked out.\n",
  1708. X                theUser.ui_name);
  1709. X        else
  1710. X            quit(1,
  1711. X                "pw_replace: Data lock failure for user %s\n",
  1712. X                theUser.ui_name);
  1713. X    }
  1714. X    rc = UIReplaceEntry(newu);
  1715. X    (void) sigsetmask(oldsigs);
  1716. X    unlockuser(theUser.ui_uid);
  1717. X    if (rc < 0)
  1718. X        quit(1, "Userinfo update failure %s\n", UIErrorMessage);
  1719. X#endif
  1720. X}
  1721. X
  1722. X/*
  1723. X *    pw_cleanup - cleanup routine
  1724. X */
  1725. Xpw_cleanup()
  1726. X{
  1727. X    /* Do nothing */
  1728. X}
  1729. X/*    End pw_userinfo.c        */
  1730. END_OF_FILE
  1731. if test 6714 -ne `wc -c <'pw_userinfo.c'`; then
  1732.     echo shar: \"'pw_userinfo.c'\" unpacked with wrong size!
  1733. fi
  1734. # end of 'pw_userinfo.c'
  1735. fi
  1736. echo shar: End of archive 2 \(of 3\).
  1737. cp /dev/null ark2isdone
  1738. MISSING=""
  1739. for I in 1 2 3 ; do
  1740.     if test ! -f ark${I}isdone ; then
  1741.     MISSING="${MISSING} ${I}"
  1742.     fi
  1743. done
  1744. if test "${MISSING}" = "" ; then
  1745.     echo You have unpacked all 3 archives.
  1746.     rm -f ark[1-9]isdone
  1747. else
  1748.     echo You still need to unpack the following archives:
  1749.     echo "        " ${MISSING}
  1750. fi
  1751. ##  End of shell archive.
  1752. exit 0
  1753. -- 
  1754. Clyde Hoover (Shouter-To-Dead-Parrots)    |
  1755. UNIX/VMS Services            | "Any sufficently advanced technology 
  1756. Compuatation Center, UT Austin         | is indisguishable from a rigged demo."
  1757. clyde@emx.utexas.edu            |
  1758.  
  1759.